#pragma once
#include <cstddef>
#include <ranges>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
class Uri {

public:
  static char FromHex(char c) {
    if ('0' <= c && c <= '9')
      return c - '0';
    else if ('A' <= c && c <= 'F')
      return c - 'A' + 10;
    else [[unlikely]]
      return c;
  }

  static std::string UriDecode(std::string_view uri) {
    std::string result;
    std::size_t last_pos = 0;
    while (true) {
      auto pos = uri.find('%', last_pos);
      if (pos == uri.npos || pos + 3 > uri.size()) {
        result.append(uri.substr(last_pos));
        return result;
      }
      result.append(uri.substr(last_pos, pos - last_pos));
      char c1 = FromHex(uri[pos + 1]);
      char c2 = FromHex(uri[pos + 2]);
      if (c1 == uri[pos + 1] || c2 == uri[pos + 2]) {
        result.push_back(uri[pos]);
        result.push_back(uri[pos + 1]);
        result.push_back(uri[pos + 2]);
      } else {
        result.push_back((c1 << 4) + c2);
      }
      last_pos = pos + 3;
    }
  }

  static Uri FromString(std::string_view suri) {
    auto i = suri.find('?');
    std::string path;
    std::unordered_map<std::string, std::string> params;
    if (i != suri.npos) {
      path = suri.substr(0, i);
      auto pairs = suri.substr(i + 1);
      for (auto pa : pairs | std::views::split('&')) {
        auto pair = std::string_view(pa.begin(), pa.end());
        auto j = pair.find('=');
        if (j != pair.npos) {
          params[std::string(pair.substr(0, j))] =
              std::string(UriDecode(pair.substr(j + 1)));
        } else {
          params[std::string(pair)] = "";
        }
      }
    } else {
      path = suri;
    }
    return Uri{std::move(path), std::move(params)};
  }
  Uri() = default;
  template <class T1, class T2>
    requires std::convertible_to<T1, std::string> &&
                 std::same_as<T2, std::unordered_map<std::string, std::string>>
  Uri(T1 &&path, T2 &&params)
      : m_path(std::forward<T1>(path)), m_params(std::forward<T2>(params)) {}
  const std::string &get_path() const { return m_path; }
  const auto &get_params() const { return m_params; }
  auto &get_params() { return m_params; }

private:
  std::string m_path;
  std::unordered_map<std::string, std::string> m_params;
};